Was tut man mit einem Stück Closed-Source Software, was den einen Vorteil hat genau das zu tun was man will (und nicht mehr), aber leider den Nachteil regelmäßig abzustürzen?
Und was tut man, wenn dieses Stück der KernelMode-Treiber einer Paketfilter-Firewall ist und „abstürzen“ bedeutet dass man einen Bluescreen bekommt?
Richtig, man holt WinDbg und IDA raus und fängt an den Fehler zu suchen 😉 Dank ersterem bekommt man aus dem Crashdump (hier: Minidump) relativ schnell raus, wo der Fehler aufgetreten ist, und mit IDA bekommt man dann raus was dort passiert.
In diesem Fall wird im TDI-Modul zuerst ein Eintrag in der Liste der verbundenen Anwendungen gesucht und dann später darauf gearbeitet. Das Funktioniert wunderbar, wenn man nur einen Kern hat (laut About-Dialog ist das Ding von 2002), aber sobald man mehr hat ist nicht mehr gesagt, dass ein gefundener Eintrag 20 Instruktionen weiter immer noch da ist. Konkret ist genau das hier passiert: ICMP-Pakete haben ja keine Connection, und dementsprechend ist ihr Eintrag sofort weg nachdem der Request erledigt ist. Alle 1000 Pakete (ungefähr) hat sich das dann so ausgewirkt, dass der eigentlich gefundene Eintrag schon wieder gelöscht war, als dann versucht wurde die gecachete MD5-Summe zu lesen.
mov ecx, [ebp+fname] push ecx call connection_by_fname mov [ebp+index], eax cmp [ebp+index], 0FFFFFFFFh ; index = -1 ? jz short newapp ; unknown app mov ecx, [ebp+len] mov edx, [ebp+index] mov eax, P_conn_table mov esi, [eax+edx*4+11Ch] ; Array-Zugriff: eax+11Ch, Element edx add esi, 1D8h ; cached md5 in esi+1D8h mov edi, [ebp+md5buf] mov edx, ecx ; strncpy(edi,esi,ecx) shr ecx, 2 rep movsd mov ecx, edx and ecx, 3 rep movsb xor eax, eax jmp exit |
Was also tun? Locking geht an der Stelle nicht, das quittiert uns Windows mit einem IRQL_NOT_LESS_OR_EQUAL – wie ich leidvoll erfahren musste, nachem ich das ganze Folgende mit diesem Ansatz durchexerziert hatte.
Möglichkeit zwei ist die weit weniger garantierte Möglichkeit einfach esi auf Null zu prüfen. Dann besteht zwar immer noch die Chance, zwischen dieser Prüfung und der Verwendung danach die Daten zu verlieren, aber das passiert anscheinend wesentlich seltener. Dazu müssen wir aber Code einfügen zwischen dem Array-Zugriff und der darauffolgenden Zeile, und zwar eigentlich nur das:
test esi,esi jz short newapp |
. Leider ist da aber kein Platz (warum auch). Also durch die Binary gesucht und nach den bekannten Alignment-Blöcken gesucht. Die sind nur Füllstoff, da können wir also unseren eigenen Code unterbringen. Praktischerweise sind 12 Byte direkt hinter der problematischen Funktion verfügbar. Die kürzeste Lösung die mir eingefallen ist sieht so aus (der erste Teil steht nach dem Arrayzugriff, „space“ ist der Füllblock):
TEST esi,esi JZ newapp CALL space ... space: ADD esi, 1D8h MOV edi, [ebp+0Ch] RET |
Und jetzt fangen die Probleme an.
IDA kann zwar in Win32PE assemblieren, aber nicht für SYS-Dateien. Überhaupt hab ich keinen passenden Assembler gefunden, der sowas in-place kann. Also hab ich mich mit einem Hex-Editor (hier: Tiny Hexer) bewaffent und die passende Stelle im Code gesucht. War einfach, der CALL an connection_by_fname ist nur dort zu finden 😉 Weniger einfach war das Übersetzen des Codes in Bytecode nur mit Hilfe von einer Referenz – aber möglich, hier das was ich gebastelt hab:
85 F6 TEST esi,esi 74 1A JZ newapp E8 A5 00 00 00 CALL space ... space: 81 C6 D8 01 00 00 ADD esi, 1D8h 8B 7D 0C MOV edi, [ebp+0Ch] C3 RET |
Damit hab ich unten sogar noch 2 Byte übrig, und der Code oben passt genau in den gleichen Platz rein wie 2 Instruktionen, die ich dann weiter unten nachgebaut hab.
Ein Call ohne Stackframebehandlung mag zwar seltsam aussehen, aber es erfüllt seinen Zweck und ist wesentlich kürzer als ein JMP/32, was hier leider nötig gewesen wäre: der Sprung ist A8h lang und damit zu weit für einen JMP/8 (der ja Vorzeichenbehaftet rechnet (was ich auch erst übersehen hatte)).
Oh und, eins noch: die Checksum im PE-Header muss unbedingt angepasst werden, sonst weigert sich Windows, den Treiber zu laden.
Somit hab ich jetzt wieder einen funktionierenden Paketfilter, auch wenn ich tausende von Pings versende 🙂